package demoMod.gdxform.ui;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import demoMod.gdxform.abstracts.Container;
import demoMod.gdxform.abstracts.ScrollBar;
import demoMod.gdxform.core.FormManager;
import demoMod.gdxform.interfaces.*;

import java.util.List;

public class GScrollPane extends Container<Element> implements MouseEventSubscriber, KeyboardEventSubscriber {
    private float plainWidth;
    private float plainHeight;

    private Color borderActivatedColor = new Color(0.05882F, 0.33725F, 0.98431F, 1);
    private Color borderDeactivatedColor = Color.GRAY.cpy();

    private ScrollBar hScrollBar;
    private ScrollBar vScrollBar;

    private boolean mouseDown = false;
    private int priority;
    private boolean renderingScrollBar = false;

    public GScrollPane(float x, float y, float width, float height, float plainWidth, float plainHeight) {
        setX(x);
        setY(y);
        super.setWidth(width);
        super.setHeight(height);
        setPlainWidth(plainWidth);
        setPlainHeight(plainHeight);
        setBorder(borderActivatedColor);

        this.priority = incr.getAndIncrement();
    }

    @Override
    public void update() {
        super.update();
    }

    @Override
    public void render(SpriteBatch sb) {
        if (!this.visible()) return;
        getFrameBuffer().begin();
        sb.setColor(Color.WHITE);

        if (this.borderTexture != null) {
            sb.draw(this.borderTexture, super.getX(true) - getBorderWidth(), super.getY(true) - getBorderWidth(), getWidth() + getBorderWidth() * 2, getHeight() + getBorderWidth() * 2);
        }
        if (this.bgTexture != null) {
            sb.draw(this.bgTexture, super.getX(true), super.getY(true), getWidth(), getHeight());
        }
        sb.flush();
        getFrameBuffer().end();

        for (Element element : getElements()) {
            if (!(element instanceof Container)) {
                getFrameBuffer().begin();
            }
            element.render(sb);
            sb.flush();
            if (!(element instanceof Container)) {
                getFrameBuffer().end();
            }
        }

        getFrameBuffer().begin();
        this.renderingScrollBar = true;
        if (this.vScrollBar != null) {
            this.vScrollBar.render(sb);
        }
        if (this.hScrollBar != null) {
            this.hScrollBar.render(sb);
        }
        sb.flush();
        this.renderingScrollBar = false;
        getFrameBuffer().end();

        Texture texture = getFrameBuffer().getColorBufferTexture();
        sb.setColor(Color.WHITE);
        if (getParent() == null) {
            sb.draw(texture,
                    (int) super.getX(true) - (int) this.getBorderWidth(), (int) super.getY(true) - (int) this.getBorderWidth(),
                    0, 0,
                    (int) getWidth() + (int) this.getBorderWidth() * 2, (int) getHeight() + (int) this.getBorderWidth() * 2,
                    1, 1,
                    0,
                    (int) super.getX(true) - (int) this.getBorderWidth(), (int) super.getY(true) - (int) this.getBorderWidth(),
                    (int) getWidth() + (int) this.getBorderWidth() * 2, (int) getHeight() + (int) this.getBorderWidth() * 2,
                    false, true); //超出容器宽高范围部分的元素不渲染
        } else {
            getFrameBuffer().begin();
            sb.draw(texture,
                    (int) getParent().getSelfX(true) - (int) getParent().getBorderWidth(), (int) getParent().getSelfY(true) - (int) getParent().getBorderWidth(),
                    0, 0,
                    (int) getParent().getWidth() + (int) getParent().getBorderWidth() * 2, (int) getParent().getHeight() + (int) getParent().getBorderWidth() * 2,
                    1, 1,
                    0,
                    (int) getParent().getSelfX(true) - (int) getParent().getBorderWidth(), (int) getParent().getSelfY(true) - (int) getParent().getBorderWidth(),
                    (int) getParent().getWidth() + (int) getParent().getBorderWidth() * 2, (int) getParent().getHeight() + (int) getParent().getBorderWidth() * 2,
                    false, true); //超出容器宽高范围部分的元素不渲染
            sb.flush();
            getFrameBuffer().end();
        }
        sb.flush();
    }

    public void setHScrollBar(ScrollBar hScrollBar) {
        this.hScrollBar = hScrollBar;
        setPlainWidth(this.plainWidth);
        if (hScrollBar != null) {
            this.hScrollBar.setParent(this);
        }
    }

    public void setVScrollBar(ScrollBar vScrollBar) {
        this.vScrollBar = vScrollBar;
        setPlainHeight(this.plainHeight);
        if (vScrollBar != null) {
            this.vScrollBar.setParent(this);
        }
    }

    public ScrollBar getHScrollBar() {
        return hScrollBar;
    }

    public ScrollBar getVScrollBar() {
        return vScrollBar;
    }

    public float getPlainWidth() {
        return plainWidth;
    }

    public void setPlainWidth(float plainWidth) {
        checkPlainWidth(getWidth(), plainWidth);
        this.plainWidth = plainWidth;
    }

    public float getPlainHeight() {
        return plainHeight;
    }

    public void setPlainHeight(float plainHeight) {
        checkPlainHeight(getHeight(), plainHeight);
        this.plainHeight = plainHeight;
    }

    @Override
    public void setWidth(float width) {
        checkPlainWidth(width, getPlainWidth());
        super.setWidth(width);
    }

    @Override
    public void setHeight(float height) {
        checkPlainHeight(height, getPlainHeight());
        super.setHeight(height);
    }

    private void checkPlainWidth(float width, float plainWidth) {
        if (plainWidth < width) {
            throw new IllegalArgumentException("plainWidth must great than ScrollPane's width. ScrollPane width: " + getWidth() + ", plainWidth: " + plainWidth);
        }
        if (this.hScrollBar != null) {
            float percent = Math.max(width / plainWidth, 0.05F);
            hScrollBar.setBarLength(hScrollBar.getWidth() * percent);
        }
    }

    private void checkPlainHeight(float height, float plainHeight) {
        if (plainHeight < height) {
            throw new IllegalArgumentException("plainHeight must great than ScrollPane's height. ScrollPane height: " + getHeight() + ", plainWidth: " + plainHeight);
        }
        if (this.vScrollBar != null) {
            float percent = Math.max(height / plainHeight, 0.05F);
            vScrollBar.setBarLength(vScrollBar.getHeight() * percent);
        }
    }

    public Color getBorderActivatedColor() {
        return borderActivatedColor;
    }

    public void setBorderActivatedColor(Color borderActivatedColor) {
        this.borderActivatedColor = borderActivatedColor;
    }

    public Color getBorderDeactivatedColor() {
        return borderDeactivatedColor;
    }

    public void setBorderDeactivatedColor(Color borderDeactivatedColor) {
        this.borderDeactivatedColor = borderDeactivatedColor;
    }

    @Override
    public float getX(boolean isAbsolute) {
        return super.getX(isAbsolute) - (hScrollBar != null && !renderingScrollBar ? (plainWidth - getWidth()) * hScrollBar.getProgress() : 0);
    }

    @Override
    public float getSelfX(boolean isAbsolute) {
        return super.getX(isAbsolute);
    }

    @Override
    public float getY(boolean isAbsolute) {
        if (renderingScrollBar) {
            return super.getY(isAbsolute);
        }
        return super.getY(isAbsolute) - (plainHeight - getHeight()) * (vScrollBar != null ? (1.0F - vScrollBar.getProgress()) : 1.0F);
    }

    @Override
    public float getSelfY(boolean isAbsolute) {
        return super.getY(isAbsolute);
    }

    @Override
    public boolean keyDown(int keyCode) {
        if (!(this.visible() || this.enabled())) return false;
        if (this.activated) {
            List<Element> elements = getElements();
            for (int i = elements.size() - 1; i >= 0; i--) {
                Element element = elements.get(i);
                if (element instanceof KeyboardEventSubscriber) {
                    if (((KeyboardEventSubscriber) element).keyDown(keyCode)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public boolean keyUp(int keyCode) {
        if (!(this.visible() || this.enabled())) return false;
        if (this.activated) {
            List<Element> elements = getElements();
            for (int i = elements.size() - 1; i >= 0; i--) {
                Element element = elements.get(i);
                if (element instanceof KeyboardEventSubscriber) {
                    if (((KeyboardEventSubscriber) element).keyUp(keyCode)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public boolean keyTyped(char key) {
        if (!(this.visible() || this.enabled())) return false;
        if (this.activated) {
            List<Element> elements = getElements();
            for (int i = elements.size() - 1; i >= 0; i--) {
                Element element = elements.get(i);
                if (element instanceof KeyboardEventSubscriber) {
                    if (((KeyboardEventSubscriber) element).keyTyped(key)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private Element currentResizingElement = null;

    @Override
    public boolean clickDown(int screenX, int screenY, int button) {
        if (!this.visible()) return false;
        mouseDown = screenX >= super.getX(true) && screenX <= super.getX(true) + getWidth() &&
                Gdx.graphics.getHeight() - screenY >= super.getY(true) && Gdx.graphics.getHeight() - screenY <= super.getY(true) + getHeight();
        if (mouseDown) {
            renderingScrollBar = true;
            if (this.vScrollBar != null) {
                if (this.vScrollBar.clickDown(screenX, screenY, button)) {
                    return true;
                }
            }
            if (this.hScrollBar != null) {
                if (this.hScrollBar.clickDown(screenX, screenY, button)) {
                    return true;
                }
            }
            renderingScrollBar = false;
            List<Element> elements = getElements();
            for (int i=elements.size() - 1;i >= 0;i--) {
                Element element = elements.get(i);
                if (element instanceof Border && element.isResizable()) {
                    if (((Border) element).atBorder(screenX, Gdx.graphics.getHeight() - screenY)) {
                        currentResizingElement = element;
                        return true;
                    }
                }
                if (element instanceof MouseEventSubscriber) {
                    if (((MouseEventSubscriber) element).clickDown(screenX, screenY, button)) {
                        return true;
                    }
                }
            }
            FormManager.getInstance().getEventHooks().setCurrentFocusedElement(this);
            return true;
        }
        return false;
    }

    @Override
    public boolean clickUp(int screenX, int screenY, int button) {
        if (!this.visible()) return false;
        if (mouseDown) {
            mouseDown = false;
            renderingScrollBar = true;
            currentResizingElement = null;
            if (this.vScrollBar != null) {
                if (this.vScrollBar.clickUp(screenX, screenY, button)) {
                    return true;
                }
            }
            if (this.hScrollBar != null) {
                if (this.hScrollBar.clickUp(screenX, screenY, button)) {
                    return true;
                }
            }
            renderingScrollBar = false;
            List<Element> elements = getElements();
            for (int i=elements.size() - 1;i >= 0;i--) {
                Element element = elements.get(i);
                if (element instanceof MouseEventSubscriber) {
                    if (((MouseEventSubscriber) element).clickUp(screenX, screenY, button)) {
                        break;
                    }
                }
            }
            return true;
        }
        return false;
    }

    @Override
    public boolean mouseDragged(int screenX, int screenY) {
        if (!this.visible()) return false;
        if (this.vScrollBar != null) {
            if (this.vScrollBar.mouseDragged(screenX, screenY)) {
                return true;
            }
        }
        if (this.hScrollBar != null) {
            if (this.hScrollBar.mouseDragged(screenX, screenY)) {
                return true;
            }
        }
        List<Element> elements = getElements();
        for (int i=elements.size() - 1;i >= 0;i--) {
            Element element = elements.get(i);
            if (element instanceof MouseEventSubscriber) {
                if (((MouseEventSubscriber) element).mouseDragged(screenX, screenY)) {
                    return true;
                }
            }
        }
        if (currentResizingElement != null) {
            ((ResizeEventSubscriber) currentResizingElement).resize(Gdx.input.getDeltaX(), Gdx.input.getDeltaY(), false);
        }
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        if (!this.visible()) return false;
        if (screenX >= super.getX(true) && screenX <= super.getX(true) + getWidth() &&
                Gdx.graphics.getHeight() - screenY >= super.getY(true) && Gdx.graphics.getHeight() - screenY <= super.getY(true) + getHeight()) {
            List<Element> elements = getElements();
            for (int i=elements.size() - 1;i >= 0;i--) {
                Element element = elements.get(i);
                if (element instanceof MouseEventSubscriber) {
                    if (((MouseEventSubscriber) element).mouseMoved(screenX, screenY)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        if (!(this.visible() || this.enabled())) return false;
        if (Gdx.input.getX() >= super.getX(true) && Gdx.input.getX() <= super.getX(true) + getWidth() &&
                Gdx.graphics.getHeight() - Gdx.input.getY() >= super.getY(true) && Gdx.graphics.getHeight() - Gdx.input.getY() <= super.getY(true) + getHeight()) {
            if (this.vScrollBar != null) {
                if (this.vScrollBar.scrolled(amount)) {
                    return true;
                }
            }
            if (this.hScrollBar != null) {
                if (this.hScrollBar.scrolled(amount)) {
                    return true;
                }
            }
            List<Element> elements = getElements();
            for (int i=elements.size() - 1;i >= 0;i--) {
                Element element = elements.get(i);
                if (element instanceof MouseEventSubscriber) {
                    if (((MouseEventSubscriber) element).scrolled(amount)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public boolean moveToElementBorder(Element element) {
        if (!this.visible()) return false;
        List<Element> elements = getElements();
        for (int i=elements.size() - 1;i >= 0;i--) {
            Element element1 = elements.get(i);
            if (element1 instanceof MouseEventSubscriber) {
                if (((MouseEventSubscriber) element1).moveToElementBorder(element)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void dispose() {
        super.dispose();
        if (this.hScrollBar != null) {
            this.hScrollBar.dispose();
        }
        if (this.vScrollBar != null) {
            this.vScrollBar.dispose();
        }
    }

    @Override
    public void activate() {
        super.activate();
        this.setBorder(this.borderActivatedColor);
    }

    @Override
    public void deactivate() {
        super.deactivate();
        this.setBorder(this.borderDeactivatedColor);
    }

    @Override
    public int getPriority() {
        return this.priority;
    }

    @Override
    public void setPriority(int priority) {
        this.priority = priority;
    }

    @Override
    public boolean hasFocus() {
        return true;
    }
}
